import { BlockPoint } from "../model/BlockPoint.js";
import { Form } from "../model/Form.js";
import { IPoint, Point } from "../model/Point.js";
import { Shape, SHAPES } from "../model/Shape.js";

function randomInt(): number {
    return ~~(1000 * Math.random());
}

function getRotatePosition(
    position: Point,
    form: Form,
    right: number,
    bottom: number
): IPoint {
    return {
        x: Math.min(position.x, right - form.width),
        y: Math.min(position.y, bottom - form.height)
    };
}

export class Block {
    #position: Point;
    #shape: Shape;
    #formIndex: number;

    constructor(shapeIndex: number, formIndex: number = randomInt()) {
        this.#position = new Point(0, 0);
        this.#shape = SHAPES[shapeIndex % SHAPES.length];
        this.#formIndex = formIndex % this.#shape.formsCount;
    }

    rotate(width: number = Infinity, height: number = Infinity): Block {
        this.#formIndex = (this.#formIndex + 1) % this.#shape.formsCount;
        const p = getRotatePosition(this.position, this.form, width, height);
        return this.setPosition(p);
    }

    moveLeft(): this {
        if (this.left > 0) {
            this.#position = this.#position.move(-1);
        }
        return this;
    }

    moveRight(width: number = Infinity): this {
        if (this.right + 1 < width) {
            this.#position = this.#position.move(1);
        }
        return this;
    }

    moveDown(height: number = Infinity): this {
        if (this.bottom + 1 < height) {
            this.#position = this.#position.move(0, 1);
        }
        return this;
    }

    setPosition(point: IPoint): Block;
    setPosition(x: number, y: number): Block;
    setPosition(x: IPoint | number, y?: number): this {
        this.#position = typeof x === "number" ? new Point(x, y!) : new Point(x);
        return this;
    }

    get form(): Form {
        return this.#shape.getForm(this.#formIndex);
    }

    get width(): number {
        return this.form.width;
    }

    get height(): number {
        return this.form.height;
    }

    get left(): number {
        return this.position.x;
    }

    get top(): number {
        return this.position.y;
    }

    get right(): number {
        return this.left + this.width - 1;
    }

    get bottom(): number {
        return this.top + this.height - 1;
    }

    get position(): Point {
        return this.#position;
    }

    get color(): string {
        return this.#shape.color;
    }

    fastenOffset(offsetX: number, offsetY: number): BlockPoint[] {
        return this.fasten(this.left + offsetX, this.top + offsetY);
    }

    fastenRotate(width: number = Infinity, height: number = Infinity): BlockPoint[] {
        const nextIndex = (this.#formIndex + 1) % this.#shape.formsCount;
        const nextForm = this.#shape.getForm(nextIndex);
        const p = getRotatePosition(this.position, nextForm, width, height);
        return this.fasten(p.x, p.y, nextForm);
    }

    fasten(
        left: number = this.left,
        top: number = this.top,
        form: Form = this.form): BlockPoint[] {
        return form.points
            .map(point => new BlockPoint(left + point.x, top + point.y, this.color))
            .filter(t => t.x >= 0 && t.y >= 0);
    }
}

export class BlockFactory {
    static #default: BlockFactory;

    static get default(): BlockFactory {
        return this.#default ??= new BlockFactory();
    }

    create(): Block {
        return new Block(randomInt());
    }
}
